import dash
from dash import html, Input, Output, State
import feffery_antd_components as fac
import numpy as np
import pandas as pd

app = dash.Dash(__name__)

server_side_df = pd.DataFrame({
    '组合排序示例1': np.random.randint(0, 100, 1000),
    '组合排序示例2': np.random.randint(0, 100, 1000),
    'checkbox筛选示例': np.random.permutation(list('abcde' * 200)),
    'keyword筛选示例': np.random.permutation(list('uvwxy' * 200))
})

app.layout = html.Div(
    [

        fac.AntdSpin(
            fac.AntdTable(
                id='table-server-side-demo',
                mode='server-side',
                columns=[
                    {
                        'title': '组合排序示例1',
                        'dataIndex': '组合排序示例1'
                    },
                    {
                        'title': '组合排序示例2',
                        'dataIndex': '组合排序示例2'
                    },
                    {
                        'title': 'checkbox筛选示例',
                        'dataIndex': 'checkbox筛选示例'
                    },
                    {
                        'title': 'keyword筛选示例',
                        'dataIndex': 'keyword筛选示例'
                    }
                ],
                data=server_side_df.head(5).to_dict('records'),
                sortOptions={
                    'sortDataIndexes': ['组合排序示例1', '组合排序示例2'],
                    'multiple': True
                },
                filterOptions={
                    'checkbox筛选示例': {
                        'filterMode': 'checkbox',
                        'filterCustomItems': list('abcde')
                    },
                    'keyword筛选示例': {
                        'filterMode': 'keyword'
                    }
                },
                bordered=True,
                pagination={
                    'current': 1,
                    'total': server_side_df.shape[0],
                    'pageSize': 5,
                    'pageSizeOptions': [5, 10]
                },
                containerId='docs-content'  # 绑定局部滚动容器以确保悬浮层正常显示
            ),
            text='回调中'
        )
    ],
    style={
        'padding': '50px'
    }
)


@app.callback(
    [Output('table-server-side-demo', 'data'),
     Output('table-server-side-demo', 'pagination')],
    [Input('table-server-side-demo', 'pagination'),
     Input('table-server-side-demo', 'sorter'),
     Input('table-server-side-demo', 'filter')],
    State('table-server-side-demo', 'filterOptions'),
    prevent_initial_call=True
)
def table_server_side_callback_demo(pagination,
                                    sorter,
                                    filter,
                                    filterOptions):
    ctx = dash.callback_context

    # 构造临时副本数据框
    batch_df = server_side_df.copy()

    # 检查是否存在未清除的筛选操作，若有，则进行离线筛选操作
    if filter:

        for key, value in filter.items():
            # 若对应字段当前存在筛选条件
            if value:
                if 'filterMode' in filterOptions[key].keys():

                    if filterOptions[key]['filterMode'] == 'checkbox':
                        batch_df = batch_df.loc[batch_df[key].isin(value), :]
                    elif filterOptions[key]['filterMode'] == 'keyword':
                        batch_df = batch_df.loc[batch_df[key].astype('str').str.contains(value[0]), :]

                else:
                    batch_df = batch_df.loc[batch_df[key].isin(value), :]

    # 检查是否存在未清除的排序操作，若有，则进行离线排序操作
    if sorter and sorter['columns'].__len__() != 0:
        # 将sorter参数中的信息转义为迎合pandas中参数ascending的bool值
        ascending = list(map(lambda order: True if order == 'ascend' else False, sorter['orders']))

        # 若没有字段参与排序，则直接返回batch_df的对应页数据帧，从而结束本次回调
        if ascending.__len__() == 0:
            return batch_df.iloc[(pagination['current'] - 1) * pagination['pageSize']
                                 :
                                 pagination['current'] * pagination['pageSize'], :].to_dict('records')

        # 对batch_df按照抽取出的条件进行排序
        (
            batch_df
                .sort_values(
                sorter['columns'],
                ascending=ascending,
                inplace=True
            )
        )

    # 若本次回调由筛选或排序操作触发，按照当前的条件组合更新pagination参数
    if ctx.triggered[0]['prop_id'] in ['table-server-side-demo.sorter', 'table-server-side-demo.filter']:
        pagination = {
            **pagination,
            **{
                'current': 1,
                'pageSize': 5,
                'total': batch_df.shape[0]
            }
        }

        # 在前面的条件组合基础上，输出对应页的数据帧
        start_index = (pagination['current'] - 1) * pagination['pageSize']
        end_index = pagination['current'] * pagination['pageSize']

        # 更新data与pagination参数
        return (
            batch_df.iloc[start_index:end_index, :].to_dict('records'),
            pagination
        )

    # 若本次回调由翻页操作触发，则只返回data，pagination返回dash.no_update（因为pagination在前端用户操作时已修改，这里避免产生环形回调）
    elif ctx.triggered[0]['prop_id'] == 'table-server-side-demo.pagination':

        # 在前面的条件组合基础上，输出对应页的数据帧
        start_index = (pagination['current'] - 1) * pagination['pageSize']
        end_index = pagination['current'] * pagination['pageSize']

        # 更新data与pagination参数
        return (
            batch_df.iloc[start_index:end_index, :].to_dict('records'),
            dash.no_update
        )

    return dash.no_update


if __name__ == '__main__':
    app.run_server(debug=True)
